package com.example.demo.service;

import com.example.demo.entity.Order;
import com.example.demo.entity.Product;
import com.example.demo.service.shop.OrderService;
import com.example.demo.service.shop.ProductService;
import com.example.demo.util.CronUtils;

import lombok.extern.slf4j.Slf4j;

import org.quartz.JobDetail;
import org.quartz.impl.JobDetailImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.IWebContext;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SeckillService {
    /**
     * 秒杀订单缓存key（冒号后面加上对应的商品id）
     * hash类型，key未用户id，value为订单id
     */
    public static final String SECKILL_ORDERS = "seckill:orders:";
    /**
     * 秒杀商品用户key（冒号后面加上对应的商品id）
     * list类型，值为用户id
     */
    public static final String SECKILL_PRODUCT_USER = "seckill:product:user:";
    /**
     * 秒杀商品用户队列key（冒号后面加上对应的商品id）
     * list类型，值为用户id
     */
    public static final String SECKILL_PRODUCT_USER_QUEUE = "seckill:product:user:queue:";
    /**
     * 秒杀商品库存key（冒号后面加上对应的商品id）
     * list类型，值为1，list长度代表秒杀商品的库存数量
     */
    public static final String SECKILL_PRODUCT_STOCK = "seckill:product:stock:";
    /**
     * 秒杀商品可用key
     * hash类型，key为商品id,value初始为"true"，表示可以，秒完设置为"false"
     */
    public static final String SECKILL_PRODUCT_FLAG = "seckill:product:flag";
    /**
     * 秒杀页面缓存key（冒号后面加上对应的视图名字,目前只做了秒杀页面的缓存）
     * string类型，value为渲染后的html字符串
     */
    public static final String SECKILL_CACHE_HTML = "seckill:cache:html:";
    @Resource
    private TemplateEngine templateEngine;
    @Resource
    private RedisTemplate redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private ProductService productService;
    @Resource
    private OrderService orderService;
    
    public boolean isSeckillUser(String userId,String productId) {
        //检查是不是秒杀商品
        if(redisTemplate.hasKey(SECKILL_PRODUCT_STOCK+productId)) {
            //检查是不是秒杀该商品的用户
            return existsUserId(userId,productId);
        }
        return false;
    }

    public String getSeckillPageFromCache() {
        return stringRedisTemplate.opsForValue()
                .get(SECKILL_CACHE_HTML+"seckillList");
    }
    public List<Product> getCachedProducts() {
        Set<String> keys = redisTemplate.keys(SECKILL_PRODUCT_STOCK+"*");
        List<Product> productList = new ArrayList<>();
        for (String key : keys) {//seckill:product:stock:商品id
            Product product = productService.findByid(new Long(key.
                            substring(key.lastIndexOf(":")+ 1)));
            product.setSeckill_start_time(product.getSeckill_start_time()-8*3600000);
            product.setSeckill_end_time(product.getSeckill_end_time() - 8 * 3600000);
            productList.add(product);
        }
        return productList;
    }
    public String cachePage(String template, IWebContext context) {
        String html = templateEngine.process(template,context);
        stringRedisTemplate.opsForValue().set(SECKILL_CACHE_HTML+"seckillList",html);
        return html;
    }

    public void createSeckill(Long productId,
                              int stock,
                              int price,
                              Long start,
                              Long end) {
        //添加到redis list中,秒杀的商品数量是跟list的长度对应，而不是跟里面值（1）对应
        //用户每秒杀该商品一次，就会取出一个数字，长度就会减一，当长度为0表示已经抢光了
        String seckillProductStockKey = SECKILL_PRODUCT_STOCK + productId;
        for (Integer i = 0; i < stock; i++) {
            redisTemplate.opsForList().leftPush(seckillProductStockKey, 1);
        }
        //创建redis hash,表示该商品是否卖完
        redisTemplate.opsForHash().put(SECKILL_PRODUCT_FLAG,""+productId,"true");
        //修改数据库
        productService.addSeckill(productId,stock,price,start,end);
    }
    /**
     * 
     * @param userId
     * @param productId
     * @return 0卖完了-1不能重复秒杀，大于0成功
     */
    @SuppressWarnings("unchecked")
	public Long doSeckill(Long userId, Long productId) {
//        System.out.println(productId + ":flag:" + redisTemplate.opsForHash().get(SECKILL_PRODUCT_FLAG,""+productId).toString());
        //从redis中检查该商品是否已经卖完
        if(!redisTemplate.opsForHash()
                .get(SECKILL_PRODUCT_FLAG,""+productId).toString().equals("true")) {
            return 0L;//已经卖完了
        }
        String uid = userId.toString();
        String pid = productId.toString();
        //是否是重复秒杀的用户（同一商品只能秒杀一次）
        if(redisTemplate.opsForSet()
        		.add(SECKILL_PRODUCT_USER + pid,uid)==0) {
        	return -1L;//
        }
        //秒杀成功关键是leftPop操作是原子的，一个线程要么调用成功，要么没有调用
        //如果调用成功，库存的list中就会减少一个数字(size减一)
        //这句if执行完成以后，有可能被挂起（会不会有问题？）
        //不会有问题，因为都是局部变量，线程之间不共享
        //因为他已经抢到了一个数字，有秒杀资格证了，
        // 所以当挂起的线程继续执行时,保存用户id业务是正确的
        if (redisTemplate.opsForList().leftPop(SECKILL_PRODUCT_STOCK + pid)!=null) {//这句if可能会被线程调度挂起，但leftPop不会
        	orderService.createSeckillOrder(userId, productId);
        	//保存用户ID到队列,没有重复的了，后续需要完成出队操作，暂未实现
        	Long index = redisTemplate.opsForList().leftPush(SECKILL_PRODUCT_USER_QUEUE, uid);
        	return index;
//        	return 1L;
        } else {
        	//没抢到，从set中移除用户
        	redisTemplate.boundSetOps(SECKILL_PRODUCT_USER+pid).remove(uid);
            //到redis中表示该商品已经卖完
            redisTemplate.opsForHash().put(SECKILL_PRODUCT_FLAG, pid,"false");
            //清除缓存的秒杀页面，重新生成（不清除也行，卖光了）
//            stringRedisTemplate.delete(SECKILL_CACHE_HTML);
            return 0L;//已经卖完了
        }
    }

    /**
     * 将抢购用户id放入队列
     * @param userId
     * @param productId
     * @return 队列的长度
     */
    public Long pushQueue(Long userId, Long productId){
        Long length = -1L;
        if(stringRedisTemplate.opsForValue()
                .get("seckill:product:queue:" + productId)==null) {
            BoundListOperations<String,String> ops =
                    stringRedisTemplate.boundListOps("seckill:user:queue:" + productId);
            length = ops.leftPush(userId.toString());
            //超过库存数量的10倍，不允许入队参与秒杀了
            if(length > redisTemplate.opsForList()
                    .size(SECKILL_PRODUCT_STOCK + productId)*10) {
                stringRedisTemplate.opsForValue().set("seckill:product:queue:" + productId,"0");
                System.out.println("队列已满：" + productId + ",length:"+length);
            }
        }
        return length;
    }

    /**
     * 从队列中取出一个用户id
     * @param productId
     * @return 用户id
     */
    public String popQueue(Long productId) {
        BoundListOperations<String,String> ops =
                stringRedisTemplate.boundListOps("seckill:user:queue:" + productId);
        return ops.rightPop(60, TimeUnit.SECONDS);
    }

    public boolean existsUserId(final String userId,final  String productId) {
        return redisTemplate.opsForSet().isMember(SECKILL_PRODUCT_USER + productId,userId);
    }

    public String getSeckillOrderId(Long userId,Long productId) {
        String orderId = (String) redisTemplate.boundHashOps(SECKILL_ORDERS+productId).get(""+userId);
        if(!StringUtils.isEmpty(orderId)) {
            //订单已经创建，从缓存中删除该信息
            redisTemplate.boundHashOps(SECKILL_ORDERS+productId).delete(""+userId);
        }
        return orderId;
    }
    
    public void startSeckill(Long productId) {
    	Product product = productService.findByid(productId);
    	Date start = new Date(product.getSeckill_start_time()-60000);
    	String cron = CronUtils.formatDateByPattern(start);
    	log.info("秒杀开始激活:{}",cron);
    	CronTrigger trigger = new CronTrigger(cron);
    	JobDetail jobDetail = new JobDetailImpl();
    	
    }

	public void removeOrderId(Order order) {
		// TODO Auto-generated method stub
		redisTemplate.boundHashOps(SeckillService.SECKILL_ORDERS+order.getProduct_id())
			.delete(order.getUserId().toString());
	}
	
	public void saveOrderId(Order order) {
		redisTemplate.boundHashOps(SeckillService.SECKILL_ORDERS+order.getProduct_id())
        .put(order.getUserId().toString(), order.getId().toString());
	}
}
